<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
  <meta content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" name="viewport"/>
  <meta content="zh-cn" http-equiv="content-language"/>
  <meta content="02 Spring Bean 依赖注入常见错误（上）" name="description"/>
  <link href="/static/favicon.png" rel="icon"/>
  <title>
   02 Spring Bean 依赖注入常见错误（上）
  </title>
  <link href="/static/index.css" rel="stylesheet"/>
  <link href="/static/highlight.min.css" rel="stylesheet"/>
  <script src="/static/highlight.min.js">
  </script>
  <meta content="Hexo 4.2.0" name="generator"/>
  <script async="" data-website-id="83e5d5db-9d06-40e3-b780-cbae722fdf8c" defer="" src="https://analyze.lianglianglee.com/umami.js">
  </script>
 </head>
 <body>
  <div class="book-container">
   <div class="book-sidebar">
    <div class="book-brand">
     <a href="/">
      <img src="/static/favicon.png"/>
      <span>
       技术文章摘抄
      </span>
     </a>
    </div>
    <div class="book-menu uncollapsible">
     <ul class="uncollapsible">
      <li>
       <a class="current-tab" href="/">
        首页
       </a>
      </li>
      <li>
       <a href="../">
        上一级
       </a>
      </li>
     </ul>
     <ul class="uncollapsible">
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/00%20%e5%af%bc%e8%af%bb%205%e5%88%86%e9%92%9f%e8%bd%bb%e6%9d%be%e4%ba%86%e8%a7%a3Spring%e5%9f%ba%e7%a1%80%e7%9f%a5%e8%af%86.html" id="00 导读 5分钟轻松了解Spring基础知识.md">
        00 导读 5分钟轻松了解Spring基础知识.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/00%20%e5%bc%80%e7%af%87%e8%af%8d%20%e8%b4%b4%e5%bf%83%e2%80%9c%e4%bf%9d%e5%a7%86%e2%80%9dSpring%e7%bd%a2%e5%b7%a5%e4%ba%86%e6%80%8e%e4%b9%88%e5%8a%9e%ef%bc%9f.html" id="00 开篇词 贴心“保姆”Spring罢工了怎么办？.md">
        00 开篇词 贴心“保姆”Spring罢工了怎么办？.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/01%20Spring%20Bean%20%e5%ae%9a%e4%b9%89%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="01 Spring Bean 定义常见错误.md">
        01 Spring Bean 定义常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/02%20Spring%20Bean%20%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="02 Spring Bean 依赖注入常见错误（上）.md">
        02 Spring Bean 依赖注入常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/03%20Spring%20Bean%20%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="03 Spring Bean 依赖注入常见错误（下）.md">
        03 Spring Bean 依赖注入常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/04%20Spring%20Bean%20%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="04 Spring Bean 生命周期常见错误.md">
        04 Spring Bean 生命周期常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/05%20Spring%20AOP%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="05 Spring AOP 常见错误（上）.md">
        05 Spring AOP 常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/06%20Spring%20AOP%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="06 Spring AOP 常见错误（下）.md">
        06 Spring AOP 常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/07%20Spring%e4%ba%8b%e4%bb%b6%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="07 Spring事件常见错误.md">
        07 Spring事件常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/08%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20Core%20%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="08 答疑现场：Spring Core 篇思考题合集.md">
        08 答疑现场：Spring Core 篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/09%20Spring%20Web%20URL%20%e8%a7%a3%e6%9e%90%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="09 Spring Web URL 解析常见错误.md">
        09 Spring Web URL 解析常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/10%20Spring%20Web%20Header%20%e8%a7%a3%e6%9e%90%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="10 Spring Web Header 解析常见错误.md">
        10 Spring Web Header 解析常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/11%20Spring%20Web%20Body%20%e8%bd%ac%e5%8c%96%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="11 Spring Web Body 转化常见错误.md">
        11 Spring Web Body 转化常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/12%20Spring%20Web%20%e5%8f%82%e6%95%b0%e9%aa%8c%e8%af%81%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="12 Spring Web 参数验证常见错误.md">
        12 Spring Web 参数验证常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/13%20Spring%20Web%20%e8%bf%87%e6%bb%a4%e5%99%a8%e4%bd%bf%e7%94%a8%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="13 Spring Web 过滤器使用常见错误（上）.md">
        13 Spring Web 过滤器使用常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/14%20Spring%20Web%20%e8%bf%87%e6%bb%a4%e5%99%a8%e4%bd%bf%e7%94%a8%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="14 Spring Web 过滤器使用常见错误（下）.md">
        14 Spring Web 过滤器使用常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/15%20Spring%20Security%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="15 Spring Security 常见错误.md">
        15 Spring Security 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/16%20Spring%20Exception%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="16 Spring Exception 常见错误.md">
        16 Spring Exception 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/17%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20Web%20%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="17 答疑现场：Spring Web 篇思考题合集.md">
        17 答疑现场：Spring Web 篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/18%20Spring%20Data%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="18 Spring Data 常见错误.md">
        18 Spring Data 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/19%20Spring%20%e4%ba%8b%e5%8a%a1%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="19 Spring 事务常见错误（上）.md">
        19 Spring 事务常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/20%20Spring%20%e4%ba%8b%e5%8a%a1%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="20 Spring 事务常见错误（下）.md">
        20 Spring 事务常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/21%20Spring%20Rest%20Template%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="21 Spring Rest Template 常见错误.md">
        21 Spring Rest Template 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/22%20Spring%20Test%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="22 Spring Test 常见错误.md">
        22 Spring Test 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/23%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20%e8%a1%a5%e5%85%85%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="23 答疑现场：Spring 补充篇思考题合集.md">
        23 答疑现场：Spring 补充篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e5%af%bc%e8%af%bb%205%e5%88%86%e9%92%9f%e8%bd%bb%e6%9d%be%e4%ba%86%e8%a7%a3%e4%b8%80%e4%b8%aaHTTP%e8%af%b7%e6%b1%82%e7%9a%84%e5%a4%84%e7%90%86%e8%bf%87%e7%a8%8b.html" id="导读 5分钟轻松了解一个HTTP请求的处理过程.md">
        导读 5分钟轻松了解一个HTTP请求的处理过程.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e7%9f%a5%e8%af%86%e5%9b%9e%e9%a1%be%20%e7%b3%bb%e7%bb%9f%e6%a2%b3%e7%90%86Spring%e7%bc%96%e7%a8%8b%e9%94%99%e8%af%af%e6%a0%b9%e6%ba%90.html" id="知识回顾 系统梳理Spring编程错误根源.md">
        知识回顾 系统梳理Spring编程错误根源.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e7%bb%93%e6%9d%9f%e8%af%ad%20%e9%97%ae%e9%a2%98%e6%80%bb%e6%af%94%e8%a7%a3%e5%86%b3%e5%8a%9e%e6%b3%95%e5%a4%9a.html" id="结束语 问题总比解决办法多.md">
        结束语 问题总比解决办法多.md
       </a>
      </li>
      <li>
       <a href="/assets/捐赠.md">
        捐赠
       </a>
      </li>
     </ul>
    </div>
   </div>
   <div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseleave="remove_inner()" onmouseover="add_inner()">
    <div class="sidebar-toggle-inner">
    </div>
   </div>
   <div class="off-canvas-content">
    <div class="columns">
     <div class="column col-12 col-lg-12">
      <div class="book-navbar">
       <header class="navbar">
        <section class="navbar-section">
         <a onclick="open_sidebar()">
          <i class="icon icon-menu">
          </i>
         </a>
        </section>
       </header>
      </div>
      <div class="book-content" style="max-width: 960px; margin: 0 auto;
    overflow-x: auto;
    overflow-y: hidden;">
       <div class="book-post">
        <div align="center">
         <a href="https://www.aliyun.com/minisite/goods?userCode=lc4iupk4" target="_blank">
          阿里云2C2G3M 99元/年，老用户也可以哦
         </a>
         <hr/>
        </div>
        <p align="center" id="tip">
        </p>
        <p class="title">
         02 Spring Bean 依赖注入常见错误（上）
        </p>
        <div>
         <p>
          你好，我是傅健，这节课我们来聊聊 Spring @Autowired。
         </p>
         <p>
          提及Spring的优势或特性，我们都会立马想起“
          <strong>
           控制反转、依赖注入
          </strong>
          ”这八字真言。而@Autowired正是用来支持依赖注入的核心利器之一。表面上看，它仅仅是一个注解，在使用上不应该出错。但是，在实际使用中，我们仍然会出现各式各样的错误，而且都堪称经典。所以这节课我就带着你学习下这些经典错误及其背后的原因，以防患于未然。
         </p>
         <h2 id="案例1-过多赠予-无所适从">
          案例1：过多赠予，无所适从
         </h2>
         <p>
          在使用@Autowired时，不管你是菜鸟级还是专家级的Spring使用者，都应该制造或者遭遇过类似的错误：
         </p>
         <blockquote>
          <p>
           required a single bean, but 2 were found
          </p>
         </blockquote>
         <p>
          顾名思义，我们仅需要一个Bean，但实际却提供了2个（这里的“2”在实际错误中可能是其它大于1的任何数字）。
         </p>
         <p>
          为了重现这个错误，我们可以先写一个案例来模拟下。假设我们在开发一个学籍管理系统案例，需要提供一个API根据学生的学号（ID）来移除学生，学生的信息维护肯定需要一个数据库来支撑，所以大体上可以实现如下：
         </p>
         <pre><code>@RestController
@Slf4j
@Validated
public class StudentController {
    @Autowired
    DataService dataService;

    @RequestMapping(path = "students/{id}", method = RequestMethod.DELETE)
    public void deleteStudent(@PathVariable("id") @Range(min = 1,max = 100) int id){
        dataService.deleteStudent(id);
    };
}
</code></pre>
         <p>
          其中DataService是一个接口，其实现依托于Oracle，代码示意如下：
         </p>
         <pre><code>public interface DataService {
    void deleteStudent(int id);
}

@Repository
@Slf4j
public class OracleDataService implements DataService{
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by oracle");
    }
}
</code></pre>
         <p>
          截止目前，运行并测试程序是毫无问题的。但是需求往往是源源不断的，某天我们可能接到节约成本的需求，希望把一些部分非核心的业务从Oracle迁移到社区版Cassandra，所以我们自然会先添加上一个新的DataService实现，代码如下：
         </p>
         <pre><code>@Repository
@Slf4j
public class CassandraDataService implements DataService{
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by cassandra");
    }
}
</code></pre>
         <p>
          实际上，当我们完成支持多个数据库的准备工作时，程序就已经无法启动了，报错如下：
         </p>
         <p>
          <img alt="" src="assets/b731c4a063eb453f880cb8eed3687567.jpg"/>
         </p>
         <p>
          很显然，上述报错信息正是我们这一小节讨论的错误，那么这个错误到底是怎么产生的呢？接下来我们具体分析下。
         </p>
         <h3 id="案例解析">
          案例解析
         </h3>
         <p>
          要找到这个问题的根源，我们就需要对@Autowired实现的依赖注入的原理有一定的了解。首先，我们先来了解下 @Autowired 发生的位置和核心过程。
         </p>
         <p>
          当一个Bean被构建时，核心包括两个基本步骤：
         </p>
         <ol>
          <li>
           执行AbstractAutowireCapableBeanFactory#createBeanInstance方法：通过构造器反射构造出这个Bean，在此案例中相当于构建出StudentController的实例；
          </li>
          <li>
           执行AbstractAutowireCapableBeanFactory#populate方法：填充（即设置）这个Bean，在本案例中，相当于设置StudentController实例中被@Autowired标记的dataService属性成员。
          </li>
         </ol>
         <p>
          在步骤2中，“填充”过程的关键就是执行各种BeanPostProcessor处理器，关键代码如下：
         </p>
         <pre><code>protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
      //省略非关键代码
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
          //省略非关键代码
         }
      }
   }   
}
</code></pre>
         <p>
          在上述代码执行过程中，因为StudentController含有标记为Autowired的成员属性dataService，所以会使用到AutowiredAnnotationBeanPostProcessor（BeanPostProcessor中的一种）来完成“装配”过程：找出合适的DataService的bean并设置给StudentController#dataService。如果深究这个装配过程，又可以细分为两个步骤：
         </p>
         <ol>
          <li>
           寻找出所有需要依赖注入的字段和方法，参考AutowiredAnnotationBeanPostProcessor#postProcessProperties中的代码行：
          </li>
         </ol>
         <pre><code>InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
</code></pre>
         <ol>
          <li>
           根据依赖信息寻找出依赖并完成注入，以字段注入为例，参考AutowiredFieldElement#inject方法：
          </li>
         </ol>
         <pre><code>@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Field field = (Field) this.member;
   Object value;
   //省略非关键代码
      try {
          DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
         //寻找“依赖”，desc为"dataService"的DependencyDescriptor
         value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
      }
      
   }
   //省略非关键代码
   if (value != null) {
      ReflectionUtils.makeAccessible(field);
      //装配“依赖”
      field.set(bean, value);
   }
}
</code></pre>
         <p>
          说到这里，我们基本了解了@Autowired过程发生的位置和过程。而且很明显，我们案例中的错误就发生在上述“寻找依赖”的过程中（上述代码的第9行），那么到底是怎么发生的呢？我们可以继续刨根问底。
         </p>
         <p>
          为了更清晰地展示错误发生的位置，我们可以采用调试的视角展示其位置（即DefaultListableBeanFactory#doResolveDependency中代码片段），参考下图：
         </p>
         <p>
          <img alt="" src="assets/96842c40dffc44cc818fc225c563c909.jpg"/>
         </p>
         <p>
          如上图所示，当我们根据DataService这个类型来找出依赖时，我们会找出2个依赖，分别为CassandraDataService和OracleDataService。在这样的情况下，如果同时满足以下两个条件则会抛出本案例的错误：
         </p>
         <ol>
          <li>
           调用determineAutowireCandidate方法来选出优先级最高的依赖，但是发现并没有优先级可依据。具体选择过程可参考DefaultListableBeanFactory#determineAutowireCandidate：
          </li>
         </ol>
         <pre><code>protected String determineAutowireCandidate(Map&lt;String, Object&gt; candidates, DependencyDescriptor descriptor) {
   Class&lt;?&gt; requiredType = descriptor.getDependencyType();
   String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
   if (primaryCandidate != null) {
      return primaryCandidate;
   }
   String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
   if (priorityCandidate != null) {
      return priorityCandidate;
   }
   // Fallback
   for (Map.Entry&lt;String, Object&gt; entry : candidates.entrySet()) {
      String candidateName = entry.getKey();
      Object beanInstance = entry.getValue();
      if ((beanInstance != null &amp;&amp; this.resolvableDependencies.containsValue(beanInstance)) ||
            matchesBeanName(candidateName, descriptor.getDependencyName())) {
         return candidateName;
      }
   }
   return null;
}
</code></pre>
         <p>
          如代码所示，优先级的决策是先根据@Primary来决策，其次是@Priority决策，最后是根据Bean名字的严格匹配来决策。如果这些帮助决策优先级的注解都没有被使用，名字也不精确匹配，则返回null，告知无法决策出哪种最合适。
         </p>
         <ol>
          <li>
           @Autowired要求是必须注入的（即required保持默认值为true），或者注解的属性类型并不是可以接受多个Bean的类型，例如数组、Map、集合。这点可以参考DefaultListableBeanFactory#indicatesMultipleBeans的实现：
          </li>
         </ol>
         <pre><code>private boolean indicatesMultipleBeans(Class&lt;?&gt; type) {
   return (type.isArray() || (type.isInterface() &amp;&amp;
         (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))));
}
</code></pre>
         <p>
          对比上述两个条件和我们的案例，很明显，案例程序能满足这些条件，所以报错并不奇怪。而如果我们把这些条件想得简单点，或许更容易帮助我们去理解这个设计。就像我们遭遇多个无法比较优劣的选择，却必须选择其一时，与其偷偷地随便选择一种，还不如直接报错，起码可以避免更严重的问题发生。
         </p>
         <h3 id="问题修正">
          问题修正
         </h3>
         <p>
          针对这个案例，有了源码的剖析，我们可以很快找到解决问题的方法：
          <strong>
           打破上述两个条件中的任何一个即可，即让候选项具有优先级或压根可以不去选择。
          </strong>
          不过需要你注意的是，不是每一种条件的打破都满足实际需求，例如我们可以通过使用标记@Primary的方式来让被标记的候选者有更高优先级，从而避免报错，但是它并不一定符合业务需求，这就好比我们本身需要两种数据库都能使用，而不是顾此失彼。
         </p>
         <pre><code>@Repository
@Primary
@Slf4j
public class OracleDataService implements DataService{
    //省略非关键代码
}
</code></pre>
         <p>
          现在，请你仔细研读上述的两个条件，要同时支持多种DataService，且能在不同业务情景下精确匹配到要选择到的DataService，我们可以使用下面的方式去修改：
         </p>
         <pre><code>@Autowired
DataService oracleDataService;
</code></pre>
         <p>
          如代码所示，修改方式的精髓在于将属性名和Bean名字精确匹配，这样就可以让注入选择不犯难：需要Oracle时指定属性名为oracleDataService，需要Cassandra时则指定属性名为cassandraDataService。
         </p>
         <h2 id="案例-2-显式引用bean时首字母忽略大小写">
          案例 2：显式引用Bean时首字母忽略大小写
         </h2>
         <p>
          针对案例1的问题修正，实际上还存在另外一种常用的解决办法，即采用@Qualifier来显式指定引用的是那种服务，例如采用下面的方式：
         </p>
         <pre><code>@Autowired()
@Qualifier("cassandraDataService")
DataService dataService;
</code></pre>
         <p>
          这种方式之所以能解决问题，在于它能让寻找出的Bean只有一个（即精确匹配），所以压根不会出现后面的决策过程，可以参考DefaultListableBeanFactory#doResolveDependency：
         </p>
         <pre><code>@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set&lt;String&gt; autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
      //省略其他非关键代码
      //寻找bean过程
      Map&lt;String, Object&gt; matchingBeans = findAutowireCandidates(beanName, type, descriptor);
      if (matchingBeans.isEmpty()) {
         if (isRequired(descriptor)) {
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
         }
         return null;
      }
      //省略其他非关键代码
      if (matchingBeans.size() &gt; 1) {
         //省略多个bean的决策过程，即案例1重点介绍内容
      } 
     //省略其他非关键代码
}
</code></pre>
         <p>
          我们会使用@Qualifier指定的名称去匹配，最终只找到了唯一一个。
         </p>
         <p>
          不过在使用@Qualifier时，我们有时候会犯另一个经典的小错误，就是我们可能会忽略Bean的名称首字母大小写。这里我们把校正后的案例稍稍变形如下：
         </p>
         <pre><code>@Autowired
@Qualifier("CassandraDataService")
DataService dataService;
</code></pre>
         <p>
          运行程序，我们会报错如下：
         </p>
         <blockquote>
          <p>
           Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘studentController’: Unsatisfied dependency expressed through field ‘dataService’; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.spring.puzzle.class2.example2.DataService’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=CassandraDataService)}
          </p>
         </blockquote>
         <p>
          这里我们很容易得出一个结论：
          <strong>
           对于Bean的名字，如果没有显式指明，就应该是类名，不过首字母应该小写。
          </strong>
          但是这个轻松得出的结论成立么？
         </p>
         <p>
          不妨再测试下，假设我们需要支持SQLite这种数据库，我们定义了一个命名为SQLiteDataService的实现，然后借鉴之前的经验，我们很容易使用下面的代码来引用这个实现：
         </p>
         <pre><code>@Autowired
@Qualifier("sQLiteDataService")
DataService dataService;
</code></pre>
         <p>
          满怀信心运行完上面的程序，依然会出现之前的错误，而如果改成SQLiteDataService，则运行通过了。这和之前的结论又矛盾了。所以，显式引用Bean时，首字母到底是大写还是小写呢？
         </p>
         <h3 id="案例解析-1">
          案例解析
         </h3>
         <p>
          对于这种错误的报错位置，其实我们正好在本案例的开头就贴出了（即第二段代码清单的第9行）：
         </p>
         <pre><code>raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
</code></pre>
         <p>
          即当因为名称问题（例如引用Bean首字母搞错了）找不到Bean时，会直接抛出NoSuchBeanDefinitionException。
         </p>
         <p>
          在这里，我们真正需要关心的问题是：不显式设置名字的Bean，其默认名称首字母到底是大写还是小写呢？
         </p>
         <p>
          看案例的话，当我们启动基于Spring Boot的应用程序时，会自动扫描我们的Package，以找出直接或间接标记了@Component的Bean的定义（即BeanDefinition）。例如CassandraDataService、SQLiteDataService都被标记了@Repository，而Repository本身被@Component标记，所以它们都是间接标记了@Component。
         </p>
         <p>
          一旦找出这些Bean的信息，就可以生成这些Bean的名字，然后组合成一个个BeanDefinitionHolder返回给上层。这个过程关键步骤可以查看下图的代码片段（ClassPathBeanDefinitionScanner#doScan）：
         </p>
         <p>
          <img alt="" src="assets/96ad15af93994cdab1a43b3a03f9f286.jpg"/>
         </p>
         <p>
          基本匹配我们前面描述的过程，其中方法调用BeanNameGenerator#generateBeanName即用来产生Bean的名字，它有两种实现方式。因为DataService的实现都是使用注解标记的，所以Bean名称的生成逻辑最终调用的其实是AnnotationBeanNameGenerator#generateBeanName这种实现方式，我们可以看下它的具体实现，代码如下：
         </p>
         <pre><code>@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
   if (definition instanceof AnnotatedBeanDefinition) {
      String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
      if (StringUtils.hasText(beanName)) {
         // Explicit bean name found.
         return beanName;
      }
   }
   // Fallback: generate a unique default bean name.
   return buildDefaultBeanName(definition, registry);
}
</code></pre>
         <p>
          大体流程只有两步：看Bean有没有显式指明名称，如果有则用显式名称，如果没有则产生一个默认名称。很明显，在我们的案例中，是没有给Bean指定名字的，所以产生的Bean的名称就是生成的默认名称，查看默认名的产生方法buildDefaultBeanName，其实现如下：
         </p>
         <pre><code>protected String buildDefaultBeanName(BeanDefinition definition) {
   String beanClassName = definition.getBeanClassName();
   Assert.state(beanClassName != null, "No bean class name set");
   String shortClassName = ClassUtils.getShortName(beanClassName);
   return Introspector.decapitalize(shortClassName);
}
</code></pre>
         <p>
          首先，获取一个简短的ClassName，然后调用Introspector#decapitalize方法，设置首字母大写或小写，具体参考下面的代码实现：
         </p>
         <pre><code>public static String decapitalize(String name) {
    if (name == null || name.length() == 0) {
        return name;
    }
    if (name.length() &gt; 1 &amp;&amp; Character.isUpperCase(name.charAt(1)) &amp;&amp;
                    Character.isUpperCase(name.charAt(0))){
        return name;
    }
    char chars[] = name.toCharArray();
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);
}
</code></pre>
         <p>
          到这，我们很轻松地明白了前面两个问题出现的原因：
          <strong>
           如果一个类名是以两个大写字母开头的，则首字母不变，其它情况下默认首字母变成小写。
          </strong>
          结合我们之前的案例，SQLiteDataService的Bean，其名称应该就是类名本身，而CassandraDataService的Bean名称则变成了首字母小写（cassandraDataService）。
         </p>
         <h3 id="问题修正-1">
          问题修正
         </h3>
         <p>
          现在我们已经从源码级别了解了Bean名字产生的规则，就可以很轻松地修正案例中的两个错误了。以引用CassandraDataService类型的Bean的错误修正为例，可以采用下面这两种修改方式：
         </p>
         <ol>
          <li>
           引用处纠正首字母大小写问题：
          </li>
         </ol>
         <pre><code>@Autowired
@Qualifier("cassandraDataService")
DataService dataService;
</code></pre>
         <ol>
          <li>
           定义处显式指定Bean名字，我们可以保持引用代码不变，而通过显式指明CassandraDataService 的Bean名称为CassandraDataService来纠正这个问题。
          </li>
         </ol>
         <pre><code>@Repository("CassandraDataService")
@Slf4j
public class CassandraDataService implements DataService {
  //省略实现
}
</code></pre>
         <p>
          现在，我们的程序就可以精确匹配到要找的Bean了。比较一下这两种修改方法的话，如果你不太了解源码，不想纠结于首字母到底是大写还是小写，建议你用第二种方法去避免困扰。
         </p>
         <h2 id="案例-3-引用内部类的bean遗忘类名">
          案例 3：引用内部类的Bean遗忘类名
         </h2>
         <p>
          解决完案例2，是不是就意味着我们能搞定所有Bean的显式引用，不再犯错了呢？天真了。我们可以沿用上面的案例，稍微再添加点别的需求，例如我们需要定义一个内部类来实现一种新的DataService，代码如下：
         </p>
         <pre><code>public class StudentController {
    @Repository
    public static class InnerClassDataService implements DataService{
        @Override
        public void deleteStudent(int id) {
          //空实现
        }
    }
    //省略其他非关键代码
 }
</code></pre>
         <p>
          遇到这种情况，我们一般都会很自然地用下面的方式直接去显式引用这个Bean：
         </p>
         <pre><code>@Autowired
@Qualifier("innerClassDataService")
DataService innerClassDataService;
</code></pre>
         <p>
          很明显，有了案例2的经验，我们上来就直接采用了
          <strong>
           首字母小写
          </strong>
          以避免案例2中的错误，但这样的代码是不是就没问题了呢？实际上，仍然会报错“找不到Bean”，这是为什么？
         </p>
         <h3 id="案例解析-2">
          案例解析
         </h3>
         <p>
          实际上，我们遭遇的情况是“如何引用内部类的Bean”。解析案例2的时候，我曾经贴出了如何产生默认Bean名的方法（即AnnotationBeanNameGenerator#buildDefaultBeanName），当时我们只关注了首字母是否小写的代码片段，而在最后变换首字母之前，有一行语句是对class名字的处理，代码如下：
         </p>
         <blockquote>
          <p>
           String shortClassName = ClassUtils.getShortName(beanClassName);
          </p>
         </blockquote>
         <p>
          我们可以看下它的实现，参考ClassUtils#getShortName方法：
         </p>
         <pre><code>public static String getShortName(String className) {
   Assert.hasLength(className, "Class name must not be empty");
   int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
   int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
   if (nameEndIndex == -1) {
      nameEndIndex = className.length();
   }
   String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
   shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
   return shortName;
}
</code></pre>
         <p>
          很明显，假设我们是一个内部类，例如下面的类名：
         </p>
         <blockquote>
          <p>
           com.spring.puzzle.class2.example3.StudentController.InnerClassDataService
          </p>
         </blockquote>
         <p>
          在经过这个方法的处理后，我们得到的其实是下面这个名称：
         </p>
         <blockquote>
          <p>
           StudentController.InnerClassDataService
          </p>
         </blockquote>
         <p>
          最后经过Introspector.decapitalize的首字母变换，最终获取的Bean名称如下：
         </p>
         <blockquote>
          <p>
           studentController.InnerClassDataService
          </p>
         </blockquote>
         <p>
          所以我们在案例程序中，直接使用 innerClassDataService 自然找不到想要的Bean。
         </p>
         <h3 id="问题修正-2">
          问题修正
         </h3>
         <p>
          通过案例解析，我们很快就找到了这个内部类，Bean的引用问题顺手就修正了，如下：
         </p>
         <pre><code>@Autowired
@Qualifier("studentController.InnerClassDataService")
DataService innerClassDataService;
</code></pre>
         <p>
          这个引用看起来有些许奇怪，但实际上是可以工作的，反而直接使用 innerClassDataService 来引用倒是真的不可行。
         </p>
         <p>
          通过这个案例我们可以看出，
          <strong>
           对源码的学习是否全面决定了我们以后犯错的可能性大小。
          </strong>
          如果我们在学习案例2时，就对class名称的变化部分的源码进行了学习，那么这种错误是不容易犯的。不过有时候我们确实很难一上来就把学习开展的全面而深入，总是需要时间和错误去锤炼的。
         </p>
         <h2 id="重点回顾">
          重点回顾
         </h2>
         <p>
          看完这三个案例，我们会发现，这些错误的直接结果都是找不到合适的Bean，但是原因却不尽相同。例如案例1是因为提供的Bean过多又无法决策选择谁；案例2和案例3是因为指定的名称不规范导致引用的Bean找不到。
         </p>
         <p>
          实际上，这些错误在一些“聪明的”IDE会被提示出来，但是它们在其它一些不太智能的主流IDE中并不能被告警出来。不过悲剧的是，即使聪明的IDE也存在误报的情况，所以
          <strong>
           完全依赖IDE是不靠谱的
          </strong>
          ，毕竟这些错误都能编译过去。
         </p>
         <p>
          另外，我们的案例都是一些简化的场景，很容易看出和发现问题，而真实的场景往往复杂得多。例如对于案例1，我们的同种类型的实现，可能不是同时出现在自己的项目代码中，而是有部分实现出现在依赖的Jar库中。所以你一定要对案例背后的源码实现有一个扎实的了解，这样才能在复杂场景中去规避这些问题。
         </p>
         <h2 id="思考题">
          思考题
         </h2>
         <p>
          我们知道了通过@Qualifier可以引用想匹配的Bean，也可以直接命名属性的名称为Bean的名称来引用，这两种方式如下：
         </p>
         <pre><code>//方式1：属性命名为要装配的bean名称
@Autowired
DataService oracleDataService;

//方式2：使用@Qualifier直接引用
@Autowired
@Qualifier("oracleDataService")
DataService dataService;
</code></pre>
         <p>
          那么对于案例3的内部类引用，你觉得可以使用第1种方式做到么？例如使用如下代码：
         </p>
         <blockquote>
          <p>
           @Autowired-
DataService studentController.InnerClassDataService;
          </p>
         </blockquote>
         <p>
          期待在留言区看到你的答案，我们下节课见！
         </p>
        </div>
       </div>
       <div>
        <div id="prePage" style="float: left">
        </div>
        <div id="nextPage" style="float: right">
        </div>
       </div>
      </div>
     </div>
    </div>
    <div class="copyright">
     <hr/>
     <p>
      © 2019 - 2023
      <a href="/cdn-cgi/l/email-protection#a4c8c8c89d9095959493e4c3c9c5cdc88ac7cbc9" target="_blank">
       Liangliang Lee
      </a>
      .
                    Powered by
      <a href="https://github.com/gin-gonic/gin" target="_blank">
       gin
      </a>
      and
      <a href="https://github.com/kaiiiz/hexo-theme-book" target="_blank">
       hexo-theme-book
      </a>
      .
     </p>
    </div>
   </div>
   <a class="off-canvas-overlay" onclick="hide_canvas()">
   </a>
  </div>
  <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js">
  </script>
  <script>
   (function(){var js = "window['__CF$cv$params']={r:'824e5dd92bc5b45e',t:'MTY5OTc4NzgyNi4xODAwMDA='};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js',document.getElementsByTagName('head')[0].appendChild(_cpo);";var _0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position = 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility = 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi = _0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj = _0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if (document.readyState !== 'loading') {handler();} else if (window.addEventListener) {document.addEventListener('DOMContentLoaded', handler);} else {var prev = document.onreadystatechange || function () {};document.onreadystatechange = function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange = prev;handler();}};}})();
  </script>
  <script crossorigin="anonymous" data-cf-beacon='{"rayId":"824e5dd92bc5b45e","version":"2023.10.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","b":1}' defer="" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317">
  </script>
 </body>
 <script async="" src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756">
 </script>
 <script src="/static/index.js">
 </script>
</html>
